////////////////////////////////////////////////////////////////////////
//
//     Copyright (c) 2009-2013 Denim Group, Ltd.
//
//     The contents of this file are subject to the Mozilla Public License
//     Version 2.0 (the "License"); you may not use this file except in
//     compliance with the License. You may obtain a copy of the License at
//     http://www.mozilla.org/MPL/
//
//     Software distributed under the License is distributed on an "AS IS"
//     basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
//     License for the specific language governing rights and limitations
//     under the License.
//
//     The Original Code is ThreadFix.
//
//     The Initial Developer of the Original Code is Denim Group, Ltd.
//     Portions created by Denim Group, Ltd. are Copyright (C)
//     Denim Group, Ltd. All Rights Reserved.
//
//     Contributor(s): Denim Group, Ltd.
//
////////////////////////////////////////////////////////////////////////
package com.denimgroup.threadfix.service;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.denimgroup.threadfix.data.dao.VulnerabilityDao;
import com.denimgroup.threadfix.data.entities.Application;
import com.denimgroup.threadfix.data.entities.Finding;
import com.denimgroup.threadfix.data.entities.SurfaceLocation;
import com.denimgroup.threadfix.data.entities.Vulnerability;

@Service
@Transactional(readOnly = true)
public class VulnerabilityServiceImpl implements VulnerabilityService {
	
	private final SanitizedLogger log = new SanitizedLogger(VulnerabilityService.class);

	private VulnerabilityDao vulnerabilityDao = null;

	@Autowired
	public VulnerabilityServiceImpl(VulnerabilityDao vulnerabilityDao) {
		this.vulnerabilityDao = vulnerabilityDao;
	}

	@Override
	public Vulnerability loadVulnerability(int vulnerabilityId) {
		return vulnerabilityDao.retrieveById(vulnerabilityId);
	}

	@Override
	public List<Vulnerability> loadOpenVulnerabilities(Application application) {
		return vulnerabilityDao.retrieveAllActiveByApplication(application.getId());
	}
	
	@Override
	@Transactional(readOnly = false)
	public void markListAsFalsePositive(List<Integer> vulnerabilityIdList) {
		markVulnListFalsePositiveValue(vulnerabilityIdList, true);
	}
	
	@Override
	@Transactional(readOnly = false)
	public void markListAsNotFalsePositive(List<Integer> vulnerabilityIdList) {
		markVulnListFalsePositiveValue(vulnerabilityIdList, false);
	}
	
	private void markVulnListFalsePositiveValue(List<Integer> vulnerabilityIdList, boolean falsePositiveValue) {
		if (vulnerabilityIdList == null || vulnerabilityIdList.size() == 0)
			return;
				
		List<Vulnerability> vulns = loadVulnerabilityList(vulnerabilityIdList);
		
		if (vulns == null || vulns.size() == 0) {
			log.warn("No vulns specified to mark.");
			return;
		}
		
		if (falsePositiveValue)
			log.info("About to mark " + vulns.size() + " Vulnerabilities as false positives.");
		else
			log.info("About to mark " + vulns.size() + " Vulnerabilities as not false positives.");
		
		int count = 0;
		
		for (Vulnerability vuln : vulns) {
			if (vuln != null) {
				count++;
				vuln.setIsFalsePositive(falsePositiveValue);
				storeVulnerability(vuln);
			}
		}
		
		if (count > 0) {
			String vulnText = " Vulnerabilities";
			if (count == 1)
				vulnText = " Vulnerability";
			
			if (falsePositiveValue) {
				log.info("Marked " + count + vulnText + " as false positives.");
			} else {
				log.info("Marked " + count + vulnText + " as not false positives.");
			}
		} else {
			log.warn("Failed to mark any vulnerabilities as false positives.");
		}
	} 

	@Override
	public List<Vulnerability> loadSimilarVulnerabilities(Vulnerability vuln) {
		List<Vulnerability> vulnList = vulnerabilityDao.retrieveSimilarHashes(vuln);
		simplifyFindings(vulnList);
		return vulnList;
	}

	@Override
	public void simplifyFindings(List<Vulnerability> vulnList) {
		Finding finding = null;
		List<Finding> findingList = null;

		for (Vulnerability vuln : vulnList) {
			finding = new Finding();
			finding.setSurfaceLocation(vuln.getSurfaceLocation());

			findingList = new ArrayList<Finding>();
			findingList.add(finding);

			vuln.setFindings(findingList);
		}
	}

	@Override
	public List<Vulnerability> loadAllByGenericVulnerabilityAndApp(Vulnerability vulnerability) {
		List<Vulnerability> vulnList = vulnerabilityDao
				.retrieveAllByGenericVulnerabilityAndApp(vulnerability);
		simplifyFindings(vulnList);
		return vulnList;
	}

	@Override
	public List<Vulnerability> loadVulnerabilityList(List<Integer> vulnerabilities) {
		List<Vulnerability> vulnList = new ArrayList<Vulnerability>();
		Vulnerability vuln = null;

		for (Integer id : vulnerabilities) {
			vuln = vulnerabilityDao.retrieveById(id);
			if (vuln != null) {
				vulnList.add(vuln);
			}
		}

		return vulnList;
	}

	@Override
	@Transactional(readOnly = false)
	public void storeVulnerability(Vulnerability vulnerability) {
		vulnerabilityDao.saveOrUpdate(vulnerability);
	}

	// returns time differences (in days) in this order:
	// Opened, WAF rule generated, submitted to Defect Tracker, marked closed by
	// Defect Tracker, and found closed by scan
	@Override
	public String[] getTimeDifferences(Vulnerability vulnerability) {
		if (vulnerability == null)
			return null;
		String[] strArray = new String[5];

		strArray[0] = "0";
		strArray[1] = daysBetween(vulnerability.getOpenTime(),
				vulnerability.getWafRuleGeneratedTime());
		strArray[2] = daysBetween(vulnerability.getOpenTime(),
				vulnerability.getDefectSubmittedTime());
		strArray[3] = daysBetween(vulnerability.getOpenTime(), vulnerability.getDefectClosedTime());
		strArray[4] = daysBetween(vulnerability.getOpenTime(), vulnerability.getCloseTime());

		return strArray;
	}
	
	/**
	 * TODO time this here and in the database and decide which to use
	 */
	@Override
	public String[] getAges(List<Vulnerability> vulnerabilities) {
		if (vulnerabilities == null || vulnerabilities.size() == 0) {
			return new String[]{};
		}
		
		Calendar now = Calendar.getInstance();
		
		String[] ages = new String[vulnerabilities.size()];
		
		for (int index = 0; index < vulnerabilities.size(); index++) {
			if (vulnerabilities.get(index) == null) {
				// we should never get here
				ages[index] = "0";
			} else {
				ages[index] = daysBetween(vulnerabilities.get(0).getOpenTime(), now);
			}
		}
		
		return ages;
	}

	// For now, return the days between two Calendar objects as a String to
	// simplify passing it to the jsp.
	private String daysBetween(Calendar startDate, Calendar endDate) {
		if (startDate == null || endDate == null)
			return "";
		Calendar date = (Calendar) startDate.clone();
		Long daysBetween = (long) 0;
		while (date.before(endDate)) {
			date.add(Calendar.DAY_OF_MONTH, 1);
			daysBetween++;
		}
		return daysBetween.toString();
	}

	public SurfaceLocation getSurfaceLocationFromDynamicFinding(Vulnerability vulnerability) {
		if (vulnerability == null || vulnerability.getFindings() == null)
			return null;

		for (Finding finding : vulnerability.getFindings())
			if (finding != null && !finding.getIsStatic() && finding.getSurfaceLocation() != null)
				return finding.getSurfaceLocation();

		return null;
	}

	public List<Finding> getStaticFindings(Vulnerability vulnerability) {
		if (vulnerability == null || vulnerability.getFindings() == null)
			return null;

		List<Finding> staticFindingList = new ArrayList<Finding>();

		for (Finding finding : vulnerability.getFindings()) {
			if (finding != null && finding.getIsStatic()) {
				if (finding.getDataFlowElements() != null
						&& finding.getDataFlowElements().size() != 0) {
					Collections.sort(finding.getDataFlowElements());
					staticFindingList.add(finding);
				}
			}
		}

		return staticFindingList;
	}

	@Override
	public List<Vulnerability> getFalsePositiveVulns(Application application) {
		return getVulnsFromAppWithFalsePositiveValue(application, true);
	}

	@Override
	public List<Vulnerability> getNonFalsePositiveVulns(Application application) {
		return getVulnsFromAppWithFalsePositiveValue(application, false);
	}
	
	private List<Vulnerability> getVulnsFromAppWithFalsePositiveValue(Application application, boolean value) {
		return vulnerabilityDao.getFalsePositiveVulnCount(application,value);
	}
}